/*
Tip:
1 Automatically read the config/config.yaml configuration file.
2 If it is not saved, a template file will be used to generate a reminder for modification.
3 If there is no template file, an error message will be prompted for use

Usage:
1 Use
z1cnf.GetString(`db.host`)
z1cnf.GetInt(`db.port`)
z1cnf.GetString(`db.dbs.2`)
z1cnf.Get(``)   get all data
z1cnf.Struct   get all data

z1cnf2struct.Z1cnf2structInstance.Db.Host

2 Add config template
step 1
import (
	...
	_ "embed"
	...
}

step 2
// run the cmd " go run . " to create config.yaml,then you can use z1cnf.Get(``)
// var _ = z1cnf.GenConfigTemplate() // gen config template, just in dev
//
//go:embed config/config_template.yaml
var z1cnfContent []byte
var _ = z1cnf.SetConfigTemplate(z1cnfContent)

step 3  if you want to use Z1cnf2structInstance
// run the cmd " go generate " to generate cnf2struct as super administrator,then you can use z1cnf.Struct
// chmod +w /d/.g/runtime/mod/gitee.com/z1gotool/z1cnf@v1.5.1/internal/z1cnf2struct/z1cnf2struct.go // linux cmd
// attrib -R "D:\.g\runtime\mod\gitee.com\z1gotool\z1cnf@v1.5.1\internal\z1cnf2struct\z1cnf2struct.go" // windows cmd
// This command must be run after any config key changes.
// run the cmd " go generate " to generate cnf2struct as super administrator,then you can use z1cnf.Struct
//go:generate go run gitee.com/z1gotool/z1cnf/cnf2struct

*/

package z1cnf

import (
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"gitee.com/z1gotool/z1cnf/internal/z1cnf2struct"
	"gopkg.in/yaml.v2"
)

var (
	filePath         = `config/config.yaml`
	templatefilePath = `config/config_template.yaml`
	z1cnfInternal    = &z1cnf{}
	Struct           = z1cnf2struct.Z1cnf2structInstance
)

func init() {
	z1cnfInternal.loadConfigFile()

	_, err := os.Stat(filePath)
	if !os.IsNotExist(err) {
		err := Load2Struct(z1cnf2struct.Z1cnf2structInstance)
		if err != nil {
			panic(err)
		}
	}
}

func GenConfigTemplate() error {
	_, err := os.Stat(templatefilePath)
	if os.IsNotExist(err) {
		dir := filepath.Dir(templatefilePath)
		os.MkdirAll(dir, 0666)

		err := os.WriteFile(templatefilePath, []byte(configTemplate), 0666)
		if err != nil {
			panic(err)
		}

		log.Println(`The configuration template has been generated!,in z1cnf`)
		os.Exit(0)
	}

	return nil
}

func SetConfigTemplate(newYamlFileContent []byte) error {
	_, err := os.Stat(filePath)
	if os.IsNotExist(err) {
		dir := filepath.Dir(filePath)
		os.MkdirAll(dir, 0666)

		err := os.WriteFile(filePath, newYamlFileContent, 0666)
		if err != nil {
			panic(err)
		}

		log.Println(`Please adjust the configuration file and restart!,in z1cnf`)
		os.Exit(0)
	}

	return nil
}

func LoadConfigFile() {
	yamlFile, err := os.ReadFile(filePath)
	if err == nil {
		err = yaml.Unmarshal(yamlFile, &z1cnfInternal.data)
		if err != nil {
			panic(err)
		}
		return
	}
}

func Load2Struct(structTmp interface{}) error {
	yamlFile, err := os.ReadFile(filePath)
	if err == nil {
		err = yaml.Unmarshal(yamlFile, structTmp)
		if err != nil {
			panic(err)
		}
		return nil
	}

	panic(err)
}

func Get(key string) interface{} {
	return z1cnfInternal.get(key)
}
func GetString(key string) string {
	return z1cnfInternal.getString(key)
}
func GetInt(key string) int {
	return z1cnfInternal.getInt(key)
}
func GetBool(key string) bool {
	return z1cnfInternal.getBool(key)
}
func GetSlice(key string) []interface{} {
	return z1cnfInternal.getSlice(key)
}
func GetSliceString(key string) []string {
	return z1cnfInternal.getSliceString(key)
}
func GetSliceInt(key string) []int {
	return z1cnfInternal.getSliceInt(key)
}
func GetSliceBool(key string) []bool {
	return z1cnfInternal.getSliceBool(key)
}
func GetStringMap(key string) map[string]interface{} {
	return z1cnfInternal.getStringMap(key)
}
func GetStringMapString(key string) map[string]string {
	return z1cnfInternal.getStringMapString(key)
}
func GetStringMapInt(key string) map[string]int {
	return z1cnfInternal.getStringMapInt(key)
}
func GetStringMapBool(key string) map[string]bool {
	return z1cnfInternal.getStringMapBool(key)
}

type z1cnf struct {
	data interface{}
}

func (z *z1cnf) nestedQuery(keyPath string) interface{} {
	keys := strings.Split(keyPath, ".")
	data := z.data

	for _, key := range keys {
		switch value := data.(type) {
		case map[interface{}]interface{}:
			nextData, ok := value[key]
			if !ok {
				return nil
			}
			data = nextData
		case []interface{}:
			index, err := strconv.Atoi(key)
			if err != nil || index < 0 || index >= len(value) {
				return nil
			}
			data = value[index]
		default:
			return nil
		}
	}

	return data
}
func (z *z1cnf) get(k string) interface{} {
	if k == `` {
		return z.data
	}

	if z.nestedQuery(k) == nil {
		panic(`No corresponding index found in the configuration file. ` + k)
	}

	return z.nestedQuery(k)
}

func (z *z1cnf) getString(k string) string {
	data := z.get(k)
	if data == nil {
		return ""
	}
	str, ok := data.(string)
	if !ok {
		return ""
	}
	return str
}

func (z *z1cnf) getInt(k string) int {
	data := z.get(k)
	if data == nil {
		return 0
	}
	num, ok := data.(int)
	if !ok {
		return 0
	}
	return num
}

func (z *z1cnf) getBool(k string) bool {
	data := z.get(k)
	if data == nil {
		return false
	}
	b, ok := data.(bool)
	if !ok {
		return false
	}
	return b
}

func (z *z1cnf) getSlice(k string) []interface{} {
	data := z.get(k)
	if data == nil {
		return nil
	}
	slice, ok := data.([]interface{})
	if !ok {
		return nil
	}
	return slice
}

func (z *z1cnf) getSliceString(k string) []string {
	data := z.get(k)
	if data == nil {
		return nil
	}

	switch v := data.(type) {
	case []interface{}:
		var slice []string
		for _, item := range v {
			str, ok := item.(string)
			if ok {
				slice = append(slice, str)
			}
		}
		return slice
	default:
		return nil
	}
}

func (z *z1cnf) getSliceInt(k string) []int {
	data := z.get(k)
	if data == nil {
		return nil
	}

	var slice []int
	switch v := data.(type) {
	case []interface{}:
		for _, item := range v {
			switch num := item.(type) {
			case int:
				slice = append(slice, num)
			case float64:
				slice = append(slice, int(num))
			}
		}
	}
	return slice
}

func (z *z1cnf) getSliceBool(k string) []bool {
	data := z.get(k)
	if data == nil {
		return nil
	}

	var slice []bool
	switch v := data.(type) {
	case []interface{}:
		for _, item := range v {
			switch val := item.(type) {
			case bool:
				slice = append(slice, val)
			case string:
				b, err := strconv.ParseBool(val)
				if err == nil {
					slice = append(slice, b)
				}
			}
		}
	}
	return slice
}

func (z *z1cnf) getStringMap(k string) map[string]interface{} {
	data := z.get(k)
	if data == nil {
		return nil
	}

	result := make(map[string]interface{})
	switch v := data.(type) {
	case map[interface{}]interface{}:
		for key, val := range v {
			if strKey, ok := key.(string); ok {
				result[strKey] = val
			}
		}
	}
	return result
}
func (z *z1cnf) getStringMapString(k string) map[string]string {
	data := z.get(k)
	result := make(map[string]string)
	if data != nil {
		switch v := data.(type) {
		case map[interface{}]interface{}:
			for key, val := range v {
				if strKey, ok := key.(string); ok {
					if strVal, ok := val.(string); ok {
						result[strKey] = strVal
					}
				}
			}
		}
	}
	return result
}

func (z *z1cnf) getStringMapInt(k string) map[string]int {
	data := z.get(k)
	result := make(map[string]int)
	if data != nil {
		switch v := data.(type) {
		case map[interface{}]interface{}:
			for key, val := range v {
				if strKey, ok := key.(string); ok {
					if intVal, ok := val.(int); ok {
						result[strKey] = intVal
					}
				}
			}
		}
	}
	return result
}

func (z *z1cnf) getStringMapBool(k string) map[string]bool {
	data := z.get(k)
	result := make(map[string]bool)
	if data != nil {
		switch v := data.(type) {
		case map[interface{}]interface{}:
			for key, val := range v {
				if strKey, ok := key.(string); ok {
					if boolVal, ok := val.(bool); ok {
						result[strKey] = boolVal
					} else if strVal, ok := val.(string); ok {
						b, err := strconv.ParseBool(strVal)
						if err == nil {
							result[strKey] = b
						}
					}
				}
			}
		}
	}
	return result
}

func (z *z1cnf) loadConfigFile() {
	yamlFile, err := os.ReadFile(filePath)
	if err == nil {
		err = yaml.Unmarshal(yamlFile, &z.data)
		if err != nil {
			panic(err)
		}
		return
	}
}

var configTemplate = `db:
  host: 127.0.0.1
  prot: 3306
  user: db_user
  password: db_pwd
`
